fix(metadata): sys_metadata 的 DDL 失败必须响亮,只静默「表已存在」一种 (#4728) - #4823
Merged
Conversation
`DatabaseLoader.ensureSchema()` 过去用一个空 `catch` 吞掉全部 DDL 失败,并且照样
把 `schemaReady` 置为 `true` —— 注释里的免责理由("e.g. table already exists")
只覆盖了最良性的一种原因,却为**所有**原因开脱。权限不足 / 数据源未连上 / 列类型
冲突之后,表或新列压根不存在,而进程状态与成功路径逐字节相同,日志里一行痕迹也
没有。这正是 #4420 的形态,#4632 已把它定成规则并落地了机械检查。
改为按错误类型判别:
- 新增内部工具 `isSchemaAlreadyExistsError()`,按驱动错误码(Postgres SQLSTATE
42P07/42701/42710、MySQL ER_TABLE_EXISTS_ERROR/ER_DUP_FIELDNAME/ER_DUP_KEYNAME
及 errno、SQLite 只能靠消息)判别,并跟随 `cause` 链;凡是没有被正面识别为
「已存在」的,一律当作真实失败。
- 良性「已存在」:表确实已就绪,静默通过,并照常执行后续迁移与 ADR-0005 索引。
- 其余失败:`console.error` 上报后果(表/列未创建,后续元数据写入不持久,而服务器
仍报告健康)与修复动作(修掉驱动/数据源错误后重启),且只说一次。
- 真实失败后 `schemaReady` 不再置 `true`:启动依旧不被阻断(方法不抛),但 loader
不再声称它并不具备的就绪状态,下一次操作会重试,瞬时故障可自愈(恢复补一条 info)。
- `ensureHistorySchema()` 按同一规则对齐,两处不再一边过度静默、一边过度报错。
删除 `scripts/durability-degradation.baseline.json` 中指向本单的条目(shrink-only)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
…abase-loader-ddl-loud
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Contributor
Author
|
范围外发现(已单开,未在本 PR 修改):#4825 —— 同文件的 Generated by Claude Code |
os-zhuang
marked this pull request as ready for review
August 3, 2026 08:51
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Aug 3, 2026
This was referenced Aug 3, 2026
ADR-0049:两份
activationEvents 声明四仓零 reader —— declared-but-unenforced,且 studio 侧 z.string() 零校验
#4657
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4728
缺陷
DatabaseLoader.ensureSchema()用一个空catch吞掉全部 DDL 失败,并且照样把schemaReady置为true:注释里的免责理由只覆盖了失败原因中最良性的一种,却用它为所有原因开脱 —— 这才是缺陷本身。真实失败(权限不足 / 数据源根本没连上 / 列类型冲突)之后,表或新列压根不存在,而进程状态与成功路径逐字节相同,启动日志里一行痕迹都没有:声称已持久化、实际没落盘、系统看起来完全健康,正是 #4420 的形态。#4632 已把规则(AGENTS.md → "Degradation log levels")与机械检查立好,本处挂在 baseline 里指向本单。
改法 —— 按错误类型判别,而不是按注释里的乐观假设
新增内部工具
packages/metadata/src/utils/schema-sync-errors.ts(未从包入口导出):code42P07/42701/42710;MySQLER_TABLE_EXISTS_ERROR/ER_DUP_FIELDNAME/ER_DUP_KEYNAMEerrno1050/1060/1061code恒为无差别的SQLITE_ERROR,只能靠table … already exists/duplicate column name;Postgres 的relation "x" already exists同样命中cause链方向是刻意保守的:凡是没有被正面识别为「已存在」的,一律当作真实失败。误判为「良性」的代价是静默丢数据,误判为「真实」的代价只是多一行 error。
三条要求的落点:
catch以error上报,文案同时给出后果(sys_metadata的表/列未创建,后续每一次元数据写入都会报错、或在宽松驱动上悄悄丢列,而服务器仍报告健康)与修复动作(修掉下面那条驱动/数据源错误后重启)。按 AGENTS.md「说一次,不是每次失败写入都说」,由schemaFailureReported保证只说一次。schemaReady不再置true。 启动依旧不被阻断(该方法不抛,调用方继续走,真缺表时会在驱动那层响亮地失败),但 loader 不再声称一个它并不具备的就绪状态;下一次元数据操作会重试,所以「数据源当时还在连接」这类瞬时故障可以自愈,恢复时补一条info。这与同文件ensureHistorySchema()的形状一致 —— 这也是把「不阻断启动」变成一个响亮的、被记录的决定,而不是与成功路径同形。project_id → environment_id迁移与 ADR-0005 索引(此前良性路径会连迁移一起跳过)。顺带把
ensureHistorySchema()对齐同一规则:良性「已存在」不再每次写入都打一条error(过度使用error是镜像失败,会训练所有人跳读 error),真实失败同样只响亮一次并保持重试。两处从此一致。Baseline
scripts/durability-degradation.baseline.json中指向本单的条目已删除(该文件 shrink-only,残留会让 gate 变红),现在entries: []。验证
新增测试把两种情况都固化(只测真实失败不够 —— 那样
() => true的分类器也能通过):src/utils/schema-sync-errors.test.ts:良性 7 例(SQLite / Postgres / MySQL /cause链 / 裸字符串)+ 非良性 6 例(权限不足、ECONNREFUSED、列类型不兼容、只读库、无信号值、超深cause)。src/loaders/database-loader.test.ts→DatabaseLoader schema-sync failure reporting (#4728):真实失败响亮且文案含后果与修复动作、不置 ready 因而下次重试(此前是 1 次 syncSchema,现在 3 次)、只说一次、瞬时故障自愈并报告恢复;良性失败静默、置 ready 不重试、迁移照常执行;外加一条DISTINGUISHES the two: same call site, opposite verdicts直接钉住两者可区分;历史表两条同构用例。packages/metadata在check-type-check-coverage.mjs里是 DEBT 条目(无typecheck脚本),仍手工跑了tsc --noEmit:新增文件 0 error,改动未引入新 error(92,均为既有的 TS2835/TS7006 等)。eslint 对四个文件干净。范围严格限定在
packages/metadata+ 那条 baseline 条目;packages/spec/**零改动。🤖 Generated with Claude Code
https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
Generated by Claude Code